home *** CD-ROM | disk | FTP | other *** search
Modula Implementation | 1993-08-18 | 10.4 KB | 306 lines |
- IMPLEMENTATION MODULE POSIX2;
- __IMP_SWITCHES__
- #ifdef HM2
- #ifdef __LONG_WHOLE__
- (*$!i+: Modul muss mit $i- uebersetzt werden! *)
- (*$!w+: Modul muss mit $w- uebersetzt werden! *)
- #else
- (*$!i-: Modul muss mit $i+ uebersetzt werden! *)
- (*$!w-: Modul muss mit $w+ uebersetzt werden! *)
- #endif
- #endif
- (*****************************************************************************)
- (* "fnmatch()": *)
- (* Als Grundlage dienten die 'C'-Dateien 'glob.c' der GNU-Shell BASH und *)
- (* 'fnmatch.c/h' der GNU-Fileutils. *)
- (*---------------------------------------------------------------------------*)
- (* 13-Aug-93, Holger Kleinschmidt *)
- (*****************************************************************************)
-
- VAL_INTRINSIC
-
- FROM PORTAB IMPORT
- (* TYPE *) UNSIGNEDWORD;
-
- FROM pSTRING IMPORT
- (* PROC *) SLEN;
-
- FROM types IMPORT
- (* CONST*) EOS, XDIRSEP;
-
- (*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*)
-
- PROCEDURE fnmatch ((* EIN/ -- *) REF str : ARRAY OF CHAR;
- (* EIN/ -- *) REF pat : ARRAY OF CHAR;
- (* EIN/ -- *) flags : FNMFlags ): INTEGER;
- (*T*)
- VAR sLen, pLen : UNSIGNEDWORD;
- dot : BOOLEAN;
- escape : BOOLEAN;
- pathname : BOOLEAN;
-
- PROCEDURE match (SIDX : UNSIGNEDWORD;
- PIDX : UNSIGNEDWORD ): BOOLEAN;
- (*T*)
- VAR inverted : BOOLEAN;
- cmin : CHAR;
- cmax : CHAR;
- __REG__ cs : CHAR;
- __REG__ sidx : UNSIGNEDWORD;
- __REG__ pidx : UNSIGNEDWORD;
- __REG__ pend : UNSIGNEDWORD;
-
- BEGIN (* match *)
- sidx := SIDX;
- pidx := PIDX;
-
- WHILE pidx < pLen DO
-
- IF sidx < sLen THEN
- cs := str[sidx];
- ELSE
- cs := EOS;
- END;
-
- CASE pat[pidx] OF
- '[': IF (cs = EOS)
- OR pathname AND (cs = XDIRSEP)
- OR dot AND (cs = '.')
- AND ( (sidx = 0)
- OR pathname AND (str[sidx-1] = XDIRSEP))
- THEN
- (* Wenn der String kein Zeichen mehr enthaelt, oder ein
- * Pfadtrenner nicht ``gematched'' werden darf, oder ein Dateiname
- * mit fuehrendem Punkt nicht ``gematched'' werden darf (entweder
- * am Stringanfang oder direkt nach einem Pfadtrenner), schlaegt
- * der Vergleich fehl.
- *)
- RETURN(FALSE);
- END;
-
- INC(pidx);
- IF (pidx < pLen) AND (pat[pidx] = INVERTCHAR) THEN
- inverted := TRUE;
- INC(pidx);
- ELSE
- inverted := FALSE;
- END;
- pend := pidx;
-
- (* Ein ']' an erster Stelle, evtl. hinter einem '!', beendet nicht
- * die Menge, sondern steht fuer das zu ``matchende'' Zeichen,
- * hat also keine Spezialbedeutung. Deswegen wird das erste
- * Zeichen der Menge uebersprungen.
- * Wenn das Escapezeichen erlaubt ist, bedeutet "...\]..."
- * nicht das Ende der Menge, sondern steht fuer ein zu
- * ``matchendes'' ']'.
- *)
- REPEAT
- INC(pend);
- UNTIL (pend >= pLen) OR (pat[pend] = ']')
- AND ( NOT escape
- OR (pat[pend-1] <> ESCAPECHAR));
-
- IF pend >= pLen THEN
- (* Syntaxfehler: Menge nicht korrekt abgeschlossen *)
- RETURN(FALSE);
- END;
-
- (* Durch das Testen auf korrekten Abschluss mit ']' koennen
- * in der nachfolgenden Schleife einige Tests auf zu grosses
- * 'pidx' entfallen.
- *)
- LOOP
- IF escape AND (pat[pidx] = ESCAPECHAR) THEN
- INC(pidx);
- END;
-
- cmin := pat[pidx];
- cmax := cmin;
- INC(pidx);
-
- IF (pat[pidx] = '-') AND (pidx + 1 < pend) THEN
- (* Ein Bereich ist nur vorhanden, falls die Obergrenze
- * nicht ']' ist; in diesem Fall steht '-' fuer ein
- * Einzelzeichen, und die Klammer beendet die Menge.
- *)
- INC(pidx);
- IF escape AND (pat[pidx] = ESCAPECHAR) THEN
- INC(pidx);
- END;
- cmax := pat[pidx];
- INC(pidx);
- END;
-
- IF (cmin <= cs) AND (cs <= cmax) THEN
- (* --> MATCH *)
- IF inverted THEN
- RETURN(FALSE);
- ELSE
- pidx := pend;
- EXIT;
- END;
- ELSIF pidx = pend THEN
- (* --> NO MATCH *)
- IF inverted THEN
- EXIT;
- ELSE
- RETURN(FALSE);
- END;
- END; (* IF cmin <= cs ... *)
- END; (* LOOP *)
- INC(sidx);
- INC(pidx);
-
- |'*': REPEAT
- INC(pidx);
- UNTIL (pidx = pLen) OR (pat[pidx] <> '*');
- DEC(pidx);
-
- (* Mehrere '*' hintereinander sind aequivalent zu einem einzelnen.
- * Bis zum letzten '*' ueberlesen.
- *)
-
- IF pathname AND (cs = XDIRSEP) THEN
- (* Wenn '*' auf einen Pfadtrenner trifft, ``matched'' es nur
- * die leere Zeichenkette, d.h. der Rest des Musters muss
- * ohne '*' auf den augenblicklichen String passen.
- *)
- INC(pidx);
- ELSIF dot AND (cs = '.')
- AND ( (sidx = 0)
- OR pathname AND (str[sidx-1] = XDIRSEP))
- THEN
- RETURN(FALSE);
- ELSE
-
- (* Das Muster hinter dem '*' wird mit jedem moeglichen Reststring
- * verglichen. Das muss rekursiv geschehen, da das Restmuster
- * wiederum '*' enthalten kann (und auch jedesmal wieder auf
- * '.' und '/' geachtet werden muss).
- * Es werden soviele Rekursionsebenen aufgebaut, wie der Reststring
- * noch lang ist. Beim rekursiven Aufstieg wird dann der Vergleich
- * durchgefuehrt, wobei in jeder Ebene der Reststring mit dem Muster
- * hinter dem '*' verglichen wird.
- *
- * Der ``schlimmste'' Fall, also der mit den meisten rekursiven
- * Aufrufen, ist ein Muster folgender Art:
- *
- * pat = "*?*?*?*?*?..."
- *
- * und ein String mit mindestens soviel Zeichen, wie das Muster
- * '*' hat.
- * Die Zahl an Rekursionsaufrufen berechnet sich in diesem Fall aus:
- *
- * rcalls = 2^stars - 1 + (sLen - stars)
- *
- * wobei 'stars' die Anzahl der '*' im Muster ist und sich aus
- *
- * stars = pLen DIV 2
- *
- * ergibt.
- * Der Aufwand ist also exponentiell, falls mehrere '*' im Muster
- * vorkommen!
- *
- * Die ``schlimmste'' Rekursionstiefe ist dagegen nicht ganz so
- * wild, sie entspricht der Stringlaenge:
- *
- * rdepth = sLen
- *
- * Beispiel: str = "xxxx" (sLen =4), pat = "*?*?*?*?" (stars=4)
- *
- * Graph der Aufrufe; die Waagerechte kennzeichnet die Rekursions-
- * ebene, die Zahlen bedeuten die Anzahl der Aufrufe auf der
- * jeweiligen Ebene (haengen von der jeweiligen Laenge des Rest-
- * strings ab):
- *
- * Ebene 0: Aufruf durch 'fnmatch()'
- * |
- * V
- * Ebene 1: -----4--------
- * / | \
- * / | \
- * -1---2---3----
- * . / / \
- * . / / \
- * . ---1--1-----2-
- * /
- * /
- * Ebene 4: ---------1----
- *
- * insgesamt 15 rekursive Aufrufe.
- *
- * Die Strings, dargestellt zum Zeitpunkt des rekursiven Aufrufs:
- *
- * pat = "*?*?*?" "*?*?" "*?*?" "*?*?"
- *
- * str = "123" ............. "23" .. "3"
- * | | |
- * "23" .. "3" "3" ""
- * | | |
- * "3" "" ""
- * |
- * ""
- *
- * Falls der String laenger ist, wird die Rekursionsebene erst
- * solange linear erhoeht, bis der Reststring genauso lang wie die
- * Anzahl der '*', dann spannt sich der Baum genauso auf.
- *)
- IF (cs <> EOS) AND match(sidx+1, pidx) THEN
- RETURN(TRUE);
- END;
- INC(pidx);
- END;
-
- |'?': IF (cs = EOS)
- OR pathname AND (cs = XDIRSEP)
- OR dot AND (cs = '.')
- AND ( (sidx = 0)
- OR pathname AND (str[sidx-1] = XDIRSEP))
- THEN
- RETURN(FALSE);
- END;
- INC(sidx);
- INC(pidx);
-
- |ESCAPECHAR:
- IF escape THEN
- INC(pidx);
- END;
- IF pidx = pLen THEN
- RETURN(cs = EOS);
- ELSIF pat[pidx] <> cs THEN
- RETURN(FALSE);
- END;
- INC(sidx);
- INC(pidx);
-
- ELSE
- IF pat[pidx] <> cs THEN
- RETURN(FALSE);
- END;
- INC(sidx);
- INC(pidx);
- END; (* CASE *)
- END; (* WHILE *)
-
- (* Wenn das Muster beendet ist, muss auch der String zuende sein.*)
- RETURN(sidx = sLen);
- END match;
-
- BEGIN (* fnmatch *)
- escape := NOT (FNMNoEscape IN flags);
- pathname := FNMPathname IN flags;
- dot := FNMPeriod IN flags;
- sLen := VAL(UNSIGNEDWORD,SLEN(str));
- pLen := VAL(UNSIGNEDWORD,SLEN(pat));
-
- IF match(0, 0) THEN
- RETURN(0);
- ELSE
- RETURN(FNMNoMatch);
- END;
- END fnmatch;
-
- END POSIX2.
-